<template>
	<view class="lsj-upload">
		<!-- #ifndef MP -->
		<view id="render" ref="render" :config="config" v-html="inputHtml" :uploadParams="uploadParams"
			:renderInput="renderInput" :renderFile="renderFile" :change:config="render.initConfig"
			:change:renderFile="render.renderFileHandle" :change:uploadParams="render.uploadHandle"
			:change:renderInput="render.chooseFileHandle">
		</view>
		<!-- #endif -->
	</view>
</template>
<script>
	export default {
		name: "lsj-upload",
		props: {
			// 调试打印
			debug: {
				type: Boolean,
				default: false
			},
			// 是否去重文件（同名文件覆盖）
			distinct: {
				type: Boolean,
				default: false
			}
		},
		data() {
			return {
				isIniting: false,
				isUpdating: false,
				isProcessing: false,
				initQueue: [],
				uploadQueue: [],
				taskQueue: [],
				chooseFileSuccess: null,
				chooseFileFail: null,
				uploadEventMap: new Map,
				baseEventMap: new Map,
				arrayBufferEventMap: new Map,
				tempFilePathEventMap: new Map,
				inputHtml: '',
				inputId: '',
				option: {},
				uploadParams: '{}',
				handleName: '',
				handleType: '',
				params: {},
			};
		},
		computed: {
			config() {
				return {
					debug: this.debug,
					distinct: this.distinct
				}
			},
			renderInput() {
				return {
					id: this.inputId,
					html: this.inputHtml,
					option: this.option,
				}
			},
			renderFile() {
				return {
					name: this.handleName,
					type: this.handleType,
					params: this.params
				}
			}
		},
		methods: {
			chooseFile(e) {
				const {
					success,
					fail,
					...option
				} = e;
				if (success) {
					this.chooseFileSuccess = success;
				} else {
					this.chooseFileSuccess = null;
				}
				if (fail) {
					this.chooseFileFail = fail;
				} else {
					this.chooseFileFail = null;
				}
				this.option = option;

				// #ifndef MP
				this.inputId = +new Date;
				this.inputHtml = `<input type="file" id="lsj-file-${this.inputId}" name="lsj-file" style="opacity:0" />`;
				// #endif
				// #ifdef MP
				const {
					count = 9, type = 'all'
				} = option;
				wx.chooseMessageFile({
					count,
					type,
					success: ({
						tempFiles
					}) => {
						this.chooseFileSuccess(tempFiles.map(_ => {
							_['status'] = 'waiting';
							_['progress'] = 0;
							return _;
						}));
					},
					fail: () => {
						this.toToast(`打开失败`);
					}
				})
				// #endif
			},
			deleted(e) {
				if (!e?.name) {
					throw Error('abort ERROR: file is undefined')
				}
				this.resetHandle();
				this.handleName = e.name;
				this.handleType = 'deleted';
			},
			clear() {
				this.resetHandle();
				this.handleName = '';
				this.handleType = 'clear';
			},
			abort(e) {
				if (!e?.name) {
					throw Error('abort ERROR: file is undefined')
				}
				// #ifndef MP
				this.resetHandle();
				this.handleName = e.name;
				this.handleType = 'abort';
				// #endif
				// #ifdef MP
				try {
					this.uploadEventMap.set(params.file.name, {
						xmlRequest
					});
					const cuXmlRequest = this.uploadEventMap.get(params.file.name)?.xmlRequest;
					if (cuXmlRequest) {
						cuXmlRequest?.abort()
					}
				} catch (error) {
					console.error(error);
				}

				// #endif
			},
			wxReadFile(params, encoding) {
				uni.getFileSystemManager().readFile({
					filePath: params?.file.path,
					encoding,
					success: (res) => {
						if (params?.success && typeof params?.success === 'function') {
							return params.success({
								name: params.file.name,
								result: res.data
							})
						}
					},
					fail: params?.fail
				})
			},
			resetHandle() {
				this.handleName = '';
				this.handleType = '';
			},
			processInitQueue() {
				if (this.isIniting || this.initQueue.length === 0) {
					return;
				}
				this.isIniting = true;
				const {
					handleName,
					handleType
				} = this.initQueue.shift();
				setTimeout(() => {
					this.resetHandle();
					this.handleName = handleName;
					this.handleType = handleType;
					this.$nextTick(() => {
						this.isIniting = false;
						this.processInitQueue();
					});
				}, 300);
			},
			initHandle({
				params,
				ob,
				handleType,
				types = ['success', 'fail']
			}) {
				try {
					if (!params?.file || !params?.file?.name) {
						throw Error('ERROR: file is undefined')
					}
					const callFunctions = this.getCallBackFn(params, types);
					ob.set(params.file.name, {
						fileChunks: [], // 存储所有分片
						totalChunks: 0, // 总分片数
						receivedIndexes: new Set(), // 当前已接收的分片索引
						...callFunctions // 回调函数
					});

					this.initQueue.push({
						handleName: params.file?.name,
						handleType
					});
					if (!this.isIniting) {
						this.processInitQueue();
					}
				} catch (error) {
					console.error(error.message);
				}
			},
			getCallBackFn(params, types) {
				let o = {};
				if (types.includes('success') && params?.success && typeof params?.success === 'function') {
					o['success'] = params?.success;
				}
				if (types.includes('fail') && params?.fail && typeof params?.fail === 'function') {
					o['fail'] = params?.fail;
				}
				if (types.includes('onprogress') && params?.onprogress && typeof params?.onprogress === 'function') {
					o['onprogress'] = params?.onprogress;
				}
				return o;
			},
			addChunks(result, item) {
				const {
					chunk,
					index,
					totalChunks
				} = result;
				if (item.totalChunks === 0) {
					item.totalChunks = totalChunks;
				}
				if (!item.receivedIndexes.has(index)) {
					item.fileChunks[index] = chunk;
					item.receivedIndexes.add(index);
				}
				return item;
			},
			executeEnd(e, maps) {
				this.executeCallBackFn(e, maps, ['success', 'fail']);
				this.baseEventMap.delete(e.name);
				this.resetHandle();
			},
			executeCallBackFn(e, maps, types) {
				const o = maps.get(e.name);
				if (!o) {
					return;
				}
				const [type] = types.filter(_ => e.status === _);
				if (type && typeof o[type] === 'function') {
					o[type](e);
				}
			},
			getArrayBuffer(params) {
				if (!params?.file || !params?.file?.name) {
					throw Error('ERROR: file is undefined')
				}
				// #ifndef MP
				this.initHandle({
					params,
					handleType: 'getArrayBuffer',
					ob: this.arrayBufferEventMap
				})
				// #endif
				// #ifdef MP
				this.wxReadFile(params);
				// #endif
			},
			getArrayBufferCallBack(e) {
				if (e.status === 'fail') {
					return this.executeEnd(e, this.arrayBufferEventMap);
				}
				if (e.status === 'success') {
					const item = this.addChunks(e.result, this.arrayBufferEventMap.get(e.name))
					if (item.receivedIndexes.size === item.totalChunks) {
						this.debug && console.log('分片结束');
						const base64 = item.fileChunks.filter(chunk => chunk !== undefined).join('');
						e.result = this.base64ToArrayBuffer(base64);
						return this.executeEnd(e, this.arrayBufferEventMap);
					}
				}
			},
			getBase(params) {
				if (!params?.file || !params?.file?.name) {
					throw Error('ERROR: file is undefined')
				}
				// #ifndef MP
				this.initHandle({
					params,
					handleType: 'getBase',
					ob: this.baseEventMap
				})
				// #endif
				// #ifdef MP
				this.wxReadFile(params, 'base64');
				// #endif
			},
			/**
			 * 将 base64 转换为 File 对象
			 * @param {String} base64Data - base64字符串，可以是带前缀的，如 ...
			 * @param {String} fileName - 自定义文件名，比如 'image.png'
			 * @returns {File} 返回 File 对象
			 */
			base64ToFile(base64Data, fileName, fileType) {
			    // 如果没有前缀，你可以手动传入 mime 或根据业务设置默认值
				let mime = fileType || 'application/octet-stream'; // 默认二进制流，也可以根据情况设置如 'image/png'
				let byteString = atob(base64Data);
				let arrayBuffer = new ArrayBuffer(byteString.length);
				let uint8Array = new Uint8Array(arrayBuffer);
				for (let i = 0; i < byteString.length; i++) {
					uint8Array[i] = byteString.charCodeAt(i);
				}
				let blob = new Blob([arrayBuffer], { type: mime });
				let file = new File([blob], fileName, { type: mime });
				return file;
			},
			base64ToArrayBuffer(base64) {
				const binaryString = atob(base64);
				const bytes = new Uint8Array(binaryString.length);
				for (let i = 0; i < binaryString.length; i++) {
					bytes[i] = binaryString.charCodeAt(i);
				}
				return bytes.buffer;
			},
			base64ToPath(base64, fileName) {
				return new Promise((resolve, reject) => {
					// #ifdef APP
					const docDir = plus.io.convertLocalFileSystemURL('_doc/');
					plus.io.resolveLocalFileSystemURL(docDir, (dirEntry) => {
						dirEntry.getFile(
							fileName, {
								create: true
							},
							(fileEntry) => {
								fileEntry.createWriter(
									(writer) => {
										writer.onwrite = (e) => {
											this.debug & console.log('文件写入成功:',
												`_doc/${fileName}`);
											resolve(`_doc/${fileName}`);
										};

										writer.onerror = (e) => {
											this.debug & console.error('文件写入失败:', e.toString());
											reject(e);
										};
										writer.writeAsBinary(base64);
									},
									(error) => {
										console.error('创建文件写入器失败:', error.toString());
										reject(error);
									}
								);
							},
							(error) => {
								console.error('获取文件条目失败:', error.toString());
								reject(error);
							}
						);
					}, (error) => {
						console.error('解析目录失败:', error.toString());
						reject(error);
					});
					// #endif

					// #ifndef APP
					reject(new Error('该功能仅支持 App 端！'));
					// #endif
				});
			},
			getBaseCallBack(e) {
				if (e.status === 'fail') {
					return this.executeEnd(e, this.baseEventMap);
				}
				if (e.status === 'success') {
					const item = this.addChunks(e.result, this.baseEventMap.get(e.name))
					if (item.receivedIndexes.size === item.totalChunks) {
						this.debug && console.log('分片结束');
						e.result = item.fileChunks.filter(chunk => chunk !== undefined).join('');
						return this.executeEnd(e, this.baseEventMap);
					}
				}
			},
			processTaskQueue() {
				if (this.isIniting) {
					setTimeout(() => {
						this.processTaskQueue();
					}, 500);
					return;
				}
				if (this.isProcessing || this.taskQueue.length === 0) {
					return;
				}
				this.isProcessing = true;
				const {
					handleName,
					handleType,
					params
				} = this.taskQueue.shift();
				setTimeout(() => {
					this.resetHandle();
					this.handleName = handleName;
					this.handleType = handleType;
					this.params = params;
					this.$nextTick(() => {
						this.isProcessing = false;
						this.processTaskQueue();
					});
				}, 200);
			},
			asyncFileTempFilePath(name, tempFilePath) {
				this.taskQueue.push({
					handleType: 'asyncFileTempFilePath',
					handleName: name,
					params: {
						tempFilePath
					}
				});
				if (!this.isProcessing) {
					this.processTaskQueue();
				}
			},
			async getTempFilePathCallBack(e) {
				if (e.status === 'fail') {
					return this.executeEnd(e, this.tempFilePathEventMap);
				}
				try {
					if (e.status === 'success') {
						if (e?.result?.tempFilePath) {
							e.result = e?.result?.tempFilePath;
							return this.executeEnd(e, this.tempFilePathEventMap);
						}
						const item = this.addChunks(e.result, this.tempFilePathEventMap.get(e.name))
						if (item.receivedIndexes.size === item.totalChunks) {
							this.debug && console.log('分片结束');
							const base64 = item.fileChunks.filter(chunk => chunk !== undefined).join('');
							const arrayBuffer = this.base64ToArrayBuffer(base64);
							// #ifdef H5
								e.result = this.base64ToFile(base64, e.name, e.fileType);
							// #endif
							// #ifdef APP
								e.result = await this.base64ToPath(
									base64,
									e.name,
								);
								this.asyncFileTempFilePath(e.name, e.result);
							// #endif
							return this.executeEnd(e, this.tempFilePathEventMap);
						}
					}
				} catch (error) {
					console.log(error);
				}
			},
			getTempFilePath(params) {
				if (!params?.file || !params?.file?.name) {
					throw Error('ERROR: file is undefined')
				}
				// #ifndef MP
				this.initHandle({
					params,
					handleType: 'getTempFilePath',
					ob: this.tempFilePathEventMap
				})
				// #endif
				// #ifdef MP
				try {
					if (params?.success && typeof params?.success === 'function') {
						return params.success({
							name: params.file.name,
							result: params.file.path
						})
					}
				} catch (error) {
					if (params?.fail && typeof params?.fail === 'function') {
						return params.fail(error)
					}
				}

				// #endif
			},
			processQueue() {
				if (this.isUpdating || this.uploadQueue.length === 0) {
					return;
				}
				this.isUpdating = true;
				const form = this.uploadQueue.shift();
				setTimeout(() => {
					this.uploadParams = JSON.stringify(form);
					this.$nextTick(() => {
						this.isUpdating = false;
						this.processQueue();
					});
				}, 300);
			},
			upload(params) {
				// #ifndef MP
				try {
					if (!params?.url) {
						throw Error('upload ERROR: url is undefined')
					}
					if (!params?.file || !params?.file?.name) {
						throw Error('upload ERROR: file is undefined')
					}
					const {
						success,
						onprogress,
						fail,
						...form
					} = params;
					const callFunctions = this.getCallBackFn(params, ['success', 'fail', 'onprogress']);
					this.uploadEventMap.set(params.file.name, callFunctions);
					// 将当前上传任务加入队列
					this.uploadQueue.push(form);
					// 开始处理队列
					this.processQueue();

				} catch (error) {
					console.error(error.message);
					if (params?.fail && typeof params?.fail === 'function') {
						return params.fail(error);
					}
				}
				// #endif
				// #ifdef MP
				return this.wxUpload(params);
				// #endif
			},
			wxUpload(params) {
				// #ifdef MP
				try {
					if (!params?.url) {
						throw Error('upload ERROR: url is undefined')
					}
					if (!params?.file || !params.file?.name || !params?.file?.path) {
						throw Error('upload ERROR: file is undefined')
					}

					const {
						success,
						onprogress,
						fail,
						url,
						file,
						...form
					} = params;

					let xmlRequest = uni.uploadFile({
						url,
						filePath: file.path,
						success: e => {
							this.uploadEventMap.delete(params.file.name);
							return success(e);
						},
						fail: e => {
							this.uploadEventMap.delete(params.file.name);
							return fail(e);
						},
						...form
					});
					this.uploadEventMap.set(params.file.name, {
						xmlRequest
					});
					xmlRequest.onProgressUpdate(({
						progress = 0
					}) => {
						if (progress <= 100) {
							onprogress({
								name: params.file.name,
								progress
							})
						}
					})

				} catch (error) {
					console.error(error.message);
					if (params?.fail && typeof params?.fail === 'function') {
						return params.fail(error);
					}
				}

				// #endif

			},
			uploadCallBack(e) {
				try {
					this.executeCallBackFn(e, this.uploadEventMap, ['success', 'fail'])
					this.uploadEventMap.delete(e.name);
				} catch (error) {
					console.error('upload', error);
				}
			},
			uploadProgress(e) {
				try {
					this.debug && console.log('上传中', e);
					this.executeCallBackFn(e, this.uploadEventMap, ['onprogress'])
				} catch (error) {
					console.error('Progress', error);
				}
			},
			changeFile(files) {
				if (files?.length) {
					if (this.chooseFileSuccess && typeof this.chooseFileSuccess === 'function') {
						this.chooseFileSuccess(files);
					}
				} else if (this.chooseFileFail && typeof this.chooseFileFail === 'function') {
					this.chooseFileFail({
						status: 'fail',
						files: []
					});
				}
				this.inputHtml = '';
			},
			openDocument({
				file,
				...params
			}) {
				if (!file) {
					throw '未传入文件'
				}
				this.getTempFilePath({
					file,
					success: (e) => {
						// #ifndef H5
						uni.openDocument({
							filePath: e.result,
							success: (res) => {
								if (params?.success && typeof params?.success === 'function') {
									return params.success(res);
								}
							},
							fail: (error) => {
								console.error('打开文档失败', error);
								if (params?.fail && typeof params?.fail === 'function') {
									return params.fail(error);
								}
							}
						});
						// #endif
						// #ifdef H5
						window.open(e.result, '_blank');
						// #endif
					},
					fail: (error) => {
						console.error('打开文档失败', error);
						if (params?.fail && typeof params?.fail === 'function') {
							return params.fail(error);
						}
					}
				})

			},
			toToast(msg) {
				if (msg) {
					uni.showToast({
						title: msg,
						icon: 'none',
						duration: 3000
					})
				}
			}
		}
	}
</script>

<script lang="renderjs" module="render">
	class DefOpitons {
		constructor() {
			// 本次选择文件个数上限
			this.count = 9;
			// 单个文件大小上限
			this.size = 10;
			// input file原生属性
			this.accept = '';
			// 是否允许多选
			this.multiple = true;
			// 允许选择的格式，空等于不限制,多个以逗号隔开,例如 'png,jpg,jpeg'
			this.formats = '';
		}
	}

	export default {
		data() {
			return {
				configs: {},
				options: {},
				fileDom: null,
				files: new Map(),
				xmls: new Map()
			}
		},
		methods: {
			initConfig(config) {
				this.configs = config;
			},
			generalFail({
				name,
				methodName
			}, error) {
				this.$ownerInstance.callMethod(methodName, {
					name,
					status: 'fail',
					message: error?.message || error
				});
			},
			renderFileHandle(e) {
				switch (e?.type) {
					case 'deleted':
						if (e?.name) {
							this.configs?.debug && console.log('删除文件：', e.name);
							this.files.delete(e.name);
						}
						break;
					case 'clear':
						this.configs?.debug && console.log('清空文件');
						this.files.clear();
						break;
					case 'getBase':
						this.executeToBase(e);
						break;
					case 'getArrayBuffer':
						this.executeToArrayBuffer(e);
						break;
					case 'getTempFilePath':
						this.executeToTempFilePath(e);
						break;
					case 'asyncFileTempFilePath':
						this.files.get(e.name)['tempFilePath'] = e?.params?.tempFilePath;
						break;
					case 'abort':
						if (this.xmls.get(e.name)) {
							this.xmls.get(e.name)?.abort();
							console.log('已终止上传' + e.name);
						}
						break;
					default:
						break;
				}
			},
			async executeToTempFilePath(e) {
				const methodName = 'getTempFilePathCallBack';
				try {
					if (!this.files.has(e?.name)) {
						throw '文件未找到';
					}
					if (this.files.get(e.name)?.tempFilePath) {
						const result = {
							tempFilePath: this.files.get(e.name).tempFilePath
						}
						this.$ownerInstance.callMethod(methodName, {
							name: e.name,
							status: 'success',
							result
						})
						return;
					}
					this.executeToArrayBuffer(e, methodName);
				} catch (error) {
					console.error(error);
					this.generalFail({
						name: e.name,
						methodName
					}, error);
				}

			},
			async executeToArrayBuffer(e, methodName = 'getArrayBufferCallBack') {
				try {
					const fileType = this.files.get(e.name)?.file?.type;
					const result = await this.fileToArrayBuffer(e?.name);
					// 分片参数配置
					const chunkSize = 0.5 * 1204 * 1024; // 每片0.5M
					const totalChunks = Math.ceil(result.length / chunkSize); // 总分片数
					for (let i = 0; i < totalChunks; i++) {
						const start = i * chunkSize;
						const end = Math.min(start + chunkSize, result.length);
						const chunk = result.slice(start, end); // 当前分片数据

						this.$ownerInstance.callMethod(methodName, {
							name: e.name,
							fileType, 
							status: 'success',
							result: {
								chunk, // 当前分片数据
								index: i, // 当前分片索引
								totalChunks, // 总分片数
							}
						});
					}
				} catch (error) {
					console.error(error);
					this.generalFail({
						name: e.name,
						methodName
					}, error);
				}
			},
			async executeToBase(e, methodName = 'getBaseCallBack') {
				try {
					const fileType = this.files.get(e.name)?.file?.type;
					const result = await this.fileToBase(e?.name);
					// 分片参数配置
					const chunkSize = 0.5 * 1204 * 1024; // 每片0.5M
					const totalChunks = Math.ceil(result.length / chunkSize); // 总分片数

					for (let i = 0; i < totalChunks; i++) {
						const start = i * chunkSize;
						const end = Math.min(start + chunkSize, result.length);
						const chunk = result.slice(start, end); // 当前分片数据

						this.$ownerInstance.callMethod(methodName, {
							name: e.name,
							fileType,
							status: 'success',
							result: {
								chunk, // 当前分片数据
								index: i, // 当前分片索引
								totalChunks, // 总分片数
							}
						});
					}
				} catch (error) {
					console.error(error);
					this.generalFail({
						name: e.name,
						methodName
					}, error);
				}
			},
			chooseFileHandle(e) {
				if (!e?.html) {
					return;
				}
				this.options = Object.assign(new DefOpitons, e.option);
				if (Number(this.options?.count) < 1) {
					this.$ownerInstance.callMethod('changeFile', []);
					return;
				}
				this.configs?.debug && console.log('chooseFile', this.options);

				this.$nextTick(() => {
					this.fileDom = document.getElementById(`lsj-file-${e.id}`);
					this.fileDom.accept = this.options.accept;
					this.fileDom.multiple = Number(this.options?.count) > 1 ? this.options.multiple : false;
					this.fileDom.value = '';
					this.fileDom.onchange = async (event) => {
						const Files = event.target.files;
						let newFiles = [];
						for (let i = 0, len = Files.length; i < len; i++) {
							const file = Files[i];
							if (this.options?.count && i >= Number(this.options.count)) {
								this.$ownerInstance.callMethod('toToast', `只允许选择${this.options.count}个文件`);
								this.fileDom.value = '';
								break;
							}
							try {
								newFiles.push(await this.addFile(file));

							} catch (error) {
								console.error(error);
							}
						}
						this.$ownerInstance.callMethod('changeFile', newFiles);
					};
					this.fileDom.click();

				});


			},
			addFile(file) {
				return new Promise((resolve, reject) => {
					if (file) {
						let fileName = file.name;
						let newFileName = fileName;
						// 限制文件格式
						const suffix = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
						const formats = this.options.formats && (this.options.formats.toLowerCase());
						this.configs?.debug && console.log('添加文件', fileName, '大小', file.size, '格式', suffix);
						if (formats && !formats.includes(suffix)) {
							this.$ownerInstance.callMethod('toToast', `不支持上传${suffix.toUpperCase()}格式文件`);
							return reject();
						}
						// 限制文件大小
						if (file.size > 1024 * 1024 * Math.abs(this.options.size)) {
							this.$ownerInstance.callMethod('toToast', `文件大小请勿超过${this.options.size}M`);
							return reject();
						}
						try {
							if (!this.configs?.distinct) {
								while (this.files.has(newFileName)) {
									const lastDotIndex = fileName.lastIndexOf('.');
									let nameWithoutExt = fileName.substring(0, lastDotIndex);
									let extension = fileName.substring(lastDotIndex);
									let counter = 1;
									while (this.files.has(`${nameWithoutExt}(${counter})${extension}`)) {
										counter++;
									}
									newFileName = `${nameWithoutExt}(${counter})${extension}`;
								}
							}
						} catch (e) {
							newFileName = Date.now() + '_' + newFileName;
						}
						this.files.set(newFileName, {
							file,
							name: newFileName,
							size: file.size,
							progress: 0,
							status: 'waiting'
						});
						return resolve(this.files.get(newFileName));

					}
				})
			},
			fileToArrayBuffer(name) {
				return new Promise((resolve, reject) => {
					if (!this.files.has(name)) {
						throw '文件未找到';
					}
					if (this.files.get(name)?.base64) {
						this.configs?.debug && console.log('读取缓存二进制');
						return resolve(this.files.get(name).base64.split(',')[1]);
					}
					const file = this.files.get(name).file;
					const reader = new FileReader();
					reader.onload = (e) => {
						let base64 = e.target.result;
						let item = this.files.get(name);
						item['base64'] = base64;
						this.files.set(name, item);
						resolve(base64.split(',')[1])
					};
					reader.onerror = (e) => reject(e);
					reader.readAsDataURL(file);
				});
			},
			fileToBase(name) {
				return new Promise((resolve, reject) => {
					if (!this.files.has(name)) {
						throw '文件未找到';
					}
					// 已转过base无需重复转换
					if (this.files.get(name)?.base64) {
						this.configs?.debug && console.log('读取缓存base64');
						return resolve(this.files.get(name).base64.replace(/[\r\n]/g, ""));
					}
					const file = this.files.get(name).file;
					const reader = new FileReader();
					reader.onload = (e) => {
						let base64 = e.target.result;
						let item = this.files.get(name);
						item['base64'] = base64;
						this.files.set(name, item);
						resolve(base64.replace(/[\r\n]/g, ""));
					};
					reader.onerror = (e) => reject(e);
					reader.readAsDataURL(file);
				})
			},
			isContentTypeJson(header) {
				try {
					const contentType = header && header['Content-Type'];
					return contentType && contentType.includes('application/json');
				} catch (error) {
					return false
				}
			},
			async uploadHandle(param) {
				const params = JSON.parse(param);
				if (!params.file?.name) {
					return;
				}
				if (params.file?.status === 'onprogress') {
					console.error('该文件状态为上传中，请勿重复提交，若不需要此过滤，请前往插件源码删除该判断');
					return;
				}
				this.configs?.debug && console.log('params', params);

				if (!this.files.has(params.file?.name)) {
					this.$ownerInstance.callMethod('uploadCallBack', {
						name: params.file.name,
						status: 'fail',
						message: '文件未找到',
					});
					return;
				}
				const {
					url,
					name = 'file',
					method = 'post',
					toBase = false,
					header = {},
					formData = {},
				} = params;

				let form = new FormData();
				for (let keys in formData) {
					form.append(keys, formData[keys]);
				}

				if (toBase) {
					const base64 = await this.fileToBase(params.file.name);
					form.append(name, base64);
				} else {
					const file = this.files.get(params.file.name).file;
					form.append(name, file);
				}
				let xmlRequest = new XMLHttpRequest();
				this.xmls.set(params.file.name, xmlRequest);
				xmlRequest.upload.addEventListener(
					'progress',
					event => {
						if (event.lengthComputable) {
							let progress = Math.ceil((event.loaded * 100) / event.total)
							if (progress <= 100) {
								this.$ownerInstance.callMethod('uploadProgress', {
									status: 'onprogress',
									name: params.file.name,
									progress
								});
							}
						}
					},
					false
				);
				xmlRequest.ontimeout = () => {
					console.error('请求超时')
					this.$ownerInstance.callMethod('uploadCallBack', {
						name: params.file.name,
						status: 'fail',
						message: '请求超时'
					});
					this.xmls.delete(params.file.name);
				}
				xmlRequest.onerror = event => {
					console.error(event);
				};
				xmlRequest.onreadystatechange = event => {
					if (xmlRequest.readyState == 4) {
						this.configs?.debug && console.log('接口是否支持跨域', xmlRequest.withCredentials);
						if (xmlRequest.status == 200) {
							this.configs?.debug && console.log('上传完成：' + xmlRequest.responseText)
							this.$ownerInstance.callMethod('uploadCallBack', {
								name: params.file.name,
								status: 'success',
								message: '上传完成',
								result: xmlRequest.responseText,
							});
							this.xmls.delete(params.file.name);
							return;
						}
						console.error('ERROR：上传失败 HTTP status:' + xmlRequest.status)
						this.xmls.delete(params.file.name);
						this.$ownerInstance.callMethod('uploadCallBack', {
							name: params.file.name,
							status: 'fail',
							message: '上传失败',
							result: xmlRequest.status
						});
					}
				}
				xmlRequest.open(method, url, true);
				for (let keys in header) {
					xmlRequest.setRequestHeader(keys, header[keys])
				}
				if (this.isContentTypeJson(header)) {
					const formObject = {};
					for (let [key, value] of form.entries()) {
						formObject[key] = value;
					}
					xmlRequest.send(JSON.stringify(formObject))
				} else {
					xmlRequest.send(form)
				}
			}
		}
	}
</script>
<style scoped>
	.lsj-upload {
		position: fixed;
		z-index: -1;
		top: -100px;
	}
</style>